﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CrossLayerCommunication.FluentValidation
{
    public static class FluentValidationExtensions
    {
        #region MergeFluentValidationResult
        /// <summary>
        /// Merges the fluent validation results into the operation result thus setting the messages if any validation failed
        /// </summary>
        public static IOperationResult MergeFluentValidationResult(this IOperationResult operationResult, params IFluentValidationResult[] fluentValidations)
        {
            if (fluentValidations == null || fluentValidations.Length == 0)
                return operationResult;

            operationResult.AddMessages(fluentValidations.Where(item=>!item.ValidationWasSuccessful).Select(item=>item.ValidationMessage).ToArray());

            return operationResult;
        }

        /// <summary>
        /// Merges the fluent validation results into the operation result thus setting the messages if any validation failed
        /// </summary>
        public static IOperationResult<T> MergeFluentValidationResult<T>(this IOperationResult<T> operationResult,
            params IFluentValidationResult[] fluentValidations)
        {
            if (fluentValidations == null || fluentValidations.Length == 0)
                return operationResult;

            operationResult.AddMessages(fluentValidations.Where(item => !item.ValidationWasSuccessful).Select(item => item.ValidationMessage).ToArray());

            return operationResult;
        }
        #endregion

        #region Invalidate
        /// <summary>
        /// Invalidates a fluent validation
        /// </summary>
        public static IFluentValidation<T> Invalidate<T>(this IFluentValidation<T> fluentValidationResult)
        {
            return new FluentValidation<T>(fluentValidationResult.ValidatedValue, fluentValidationResult.ValidationMessage, false, fluentValidationResult.WasLastValidationConditionMet);
        }
        #endregion

        #region FleuntValidation
        /// <summary>
        /// Initializes a fluent validation
        /// </summary>
        /// <param name="message">Message if the validation fails</param>
        public static IFluentValidation<T> FluentValidation<T>(this T valueToValidate, IMessage message)
        {
            return new FluentValidation<T>(valueToValidate, message);
        }    
        #endregion

        #region SetNewMessage
        /// <summary>
        /// Change the message of the fluent validation if the validation is still valid
        /// </summary>
        /// <param name="message">The new validation message</param>
        public static IFluentValidation<T> SetNewMessage<T>(this IFluentValidation<T> fluentValidation, IMessage message)
        {
            if (fluentValidation.ValidationWasSuccessful)
                return new FluentValidation<T>(fluentValidation.ValidatedValue, message, fluentValidation.ValidationWasSuccessful, fluentValidation.WasLastValidationConditionMet);

            return fluentValidation;
        }        
        #endregion

        #region If
        /// <summary>
        /// Checks if condition is met to continue validation
        /// </summary>
        /// <param name="conditionFactory">Condition delegate</param>
        public static IFluentValidation<T> If<T>(this IFluentValidation<T> fluentValidation, Func<T, bool> conditionFactory)
        {
            if (fluentValidation.ValidationWasSuccessful)
                return new FluentValidation<T>(fluentValidation.ValidatedValue, fluentValidation.ValidationMessage, fluentValidation.ValidationWasSuccessful, conditionFactory(fluentValidation.ValidatedValue));
                
            return fluentValidation;            
        }
        #endregion



        #region IComparable
        #region IsOutside
        /// <summary>
        /// Checks whether the value is outside the provided boundaries exclusively
        /// </summary>
        /// <param name="startValue">lower exclusive boundary</param>
        /// <param name="endValue">upper exclusive boundary</param>
        public static IFluentValidation<T> IsOutside<T>(this IFluentValidation<T> fluentValidation, T startValue, T endValue) where T : IComparable
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.IsOutside(startValue, endValue));
        }
        /// <summary>
        /// Checks whether the value is outside the provided boundaries exclusively
        /// </summary>
        /// <param name="startValue">lower exclusive boundary</param>
        /// <param name="endValue">upper exclusive boundary</param>
        public static IFluentValidation<Nullable<T>> IsOutside<T>(this IFluentValidation<Nullable<T>> fluentValidation, T startValue, T endValue) where T : struct, IComparable
        {            
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.Value.IsOutside(startValue, endValue));
        }
        /// <summary>
        /// Checks whether the value is outside the provided boundaries exclusively
        /// </summary>
        /// <param name="startValue">lower exclusive boundary</param>
        /// <param name="endValue">upper exclusive boundary</param>
        private static bool IsOutside<T>(this T value, T startValue, T endValue) where T : IComparable
        {
            if (startValue.CompareTo(endValue) == 1)
                throw new ArgumentException();

            return value.IsSmallerThan(startValue) || value.IsGreaterThan(endValue);
        }
        #endregion

        #region IsBetween
        /// <summary>
        /// Checks whether the value is between the provided boundaries inclusively
        /// </summary>
        /// <param name="startValue">lower inclusive boundary</param>
        /// <param name="endValue">upper inclusive boundary</param>
        public static IFluentValidation<T> IsBetween<T>(this IFluentValidation<T> fluentValidation, T startValue, T endValue) where T:IComparable
        {
            return fluentValidation.PerformValidation(fv=> !fv.ValidatedValue.IsBetween(startValue, endValue));
        }
        /// <summary>
        /// Checks whether the value is between the provided boundaries inclusively
        /// </summary>
        /// <param name="startValue">lower inclusive boundary</param>
        /// <param name="endValue">upper inclusive boundary</param>
        public static IFluentValidation<Nullable<T>> IsBetween<T>(this IFluentValidation<Nullable<T>> fluentValidation, T startValue, T endValue) where T : struct, IComparable
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.Value.IsBetween(startValue, endValue));
        }
        /// <summary>
        /// Checks whether the value is between the provided boundaries inclusively
        /// </summary>
        /// <param name="startValue">lower inclusive boundary</param>
        /// <param name="endValue">upper inclusive boundary</param>
        private static bool IsBetween<T>(this T value, T startValue, T endValue) where T : IComparable
        {
            if (startValue.CompareTo(endValue) == 1)
                throw new ArgumentException();

            return (value.IsGreaterThan(startValue) || value.Equals(startValue)) && (value.IsSmallerThan(endValue) || value.Equals(endValue));
        }
        #endregion

        #region IsGreaterThan
        /// <summary>
        /// Checks whether the value is greater than exclusively
        /// </summary>
        /// <param name="lowerBound">lower exclusive boundary</param>
        public static IFluentValidation<T> IsGreaterThan<T>(this IFluentValidation<T> fluentValidation, T lowerBound) where T : IComparable
        {
            return fluentValidation.PerformValidation(fv=> !fv.ValidatedValue.IsGreaterThan(lowerBound));
        }
        /// <summary>
        /// Checks whether the value is greater than exclusively
        /// </summary>
        /// <param name="lowerBound">lower exclusive boundary</param>
        public static IFluentValidation<Nullable<T>> IsGreaterThan<T>(this IFluentValidation<Nullable<T>> fluentValidation, T lowerBound) where T : struct, IComparable
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.Value.IsGreaterThan(lowerBound));
        }
        /// <summary>
        /// Checks whether the value is greater than exclusively
        /// </summary>
        /// <param name="lowerBound">lower exclusive boundary</param>
        private static bool IsGreaterThan<T>(this T value, T lowerBound) where T:IComparable             
        {
            return value.CompareTo(lowerBound) > 0;
        }
        #endregion

        #region IsSmallerThan
        /// <summary>
        /// Checks whether the value is smaller than the provided boundary
        /// </summary>
        /// <param name="upperBound">upper exclusive boundary</param>
        public static IFluentValidation<T> IsSmallerThan<T>(this IFluentValidation<T> fluentValidation, T upperBound) where T : IComparable
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.IsSmallerThan(upperBound));            
        }
        /// <summary>
        /// Checks whether the value is smaller than the provided boundary
        /// </summary>
        /// <param name="upperBound">upper exclusive boundary</param>
        public static IFluentValidation<Nullable<T>> IsSmallerThan<T>(this IFluentValidation<Nullable<T>> fluentValidation, T upperBound) where T : struct, IComparable
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.Value.IsSmallerThan(upperBound));
        }
        /// <summary>
        /// Checks whether the value is smaller than the provided boundary
        /// </summary>
        /// <param name="upperBound">upper exclusive boundary</param>
        private static bool IsSmallerThan<T>(this T value, T upperBound) where T : IComparable
        {
            return value.CompareTo(upperBound) < 0;            
        }
        #endregion
        #endregion

        #region Equality
        /// <summary>
        /// Checks whether the value is equal to the provided value
        /// </summary>
        /// <param name="valueToEqual">value to check equality</param>
        public static IFluentValidation<T> IsEqualTo<T>(this IFluentValidation<T> fluentValidation, T valueToEqual)
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.Equals(valueToEqual));
        }
        /// <summary>
        /// Checks whether the value is not equal to the provided value
        /// </summary>
        /// <param name="valueToEqual">value to check inequality</param>
        public static IFluentValidation<T> IsNotEqualTo<T>(this IFluentValidation<T> fluentValidation, T valueToEqual)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Equals(valueToEqual));
        }        
        #endregion

        #region Null
        /// <summary>
        /// Checks whether the value is null
        /// </summary>
        public static IFluentValidation<T?> IsNotNull<T>(this IFluentValidation<T?> fluentValidation) where T : struct
        {
            return fluentValidation.PerformValidation(fv => !fv.ValidatedValue.HasValue);
        }
        /// <summary>
        /// Checks whether the value is null
        /// </summary>
        public static IFluentValidation<T?> IsNull<T>(this IFluentValidation<T?> fluentValidation) where T : struct
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.HasValue);
        }

        /// <summary>
        /// Checks whether the value is not null
        /// </summary>
        public static IFluentValidation<T> IsNotNull<T>(this IFluentValidation<T> fluentValidation) where T : class
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue == null);
        }
        /// <summary>
        /// Checks whether the value is not null
        /// </summary>
        public static IFluentValidation<T> IsNull<T>(this IFluentValidation<T> fluentValidation) where T : class
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue != null);
        }
        #endregion

        #region Custom
        /// <summary>
        /// Checks whether the value is valid by performing the provided validation
        /// </summary>
        /// <param name="validFactory"></param>
        public static IFluentValidation<T> CustomValidation<T>(this IFluentValidation<T> fluentValidation, Func<T, bool> validFactory)
        {
            return fluentValidation.PerformValidation(fv => !validFactory(fv.ValidatedValue));
        }
        #endregion

        #region Matches regex
        /// <summary>
        /// Checks whether the string matches the provided regex pattern
        /// </summary>
        /// <param name="pattern">pattern to check</param>
        public static IFluentValidation<string> MatchesRegex(this IFluentValidation<string> fluentValidation, string pattern)
        {
            return fluentValidation.PerformValidation(fv => !Regex.Match(fluentValidation.ValidatedValue, pattern).Success);
        }
        #endregion       

        #region IsDefault
        /// <summary>
        /// Checks whether the value is still default
        /// </summary>
        /// <returns></returns>
        public static IFluentValidation<T> IsDefault<T>(this IFluentValidation<T> fluentValdiation) where T:struct
        {
            return fluentValdiation.PerformValidation(fv => !default(T).Equals(fv.ValidatedValue));
        }
        /// <summary>
        /// Checks whether the value is was set
        /// </summary>
        /// <returns></returns>
        public static IFluentValidation<T> IsNotDefault<T>(this IFluentValidation<T> fluentValdiation) where T : struct
        {
            return fluentValdiation.PerformValidation(fv => default(T).Equals(fv.ValidatedValue));
        }
        #endregion

        #region Length
        /// <summary>
        /// Checks whether the collection is longer than the provided lower boundary exclusively
        /// </summary>
        /// <param name="lowerBound">lower exclusive boundary</param>
        public static IFluentValidation<ICollection> IsLongerThan(this IFluentValidation<ICollection> fluentValidation, int lowerBound)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Count <= lowerBound);
        }
        /// <summary>
        /// Checks whether the string is longer than the provided lower boundary exclusively
        /// </summary>
        /// <param name="lowerBound">lower exclusive boundary</param>
        public static IFluentValidation<string> IsLongerThan(this IFluentValidation<string> fluentValidation, int lowerBound)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Length <= lowerBound);
        }
        /// <summary>
        /// Checks whether the collection is shorter than the provided upper boundary exclusively
        /// </summary>
        /// <param name="upperBound">upper exclusive boundary</param>
        public static IFluentValidation<ICollection> IsShorterThan(this IFluentValidation<ICollection> fluentValidation, int upperBound)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Count >= upperBound);
        }
        /// <summary>
        /// Checks whether the string is shorter than the provided upper boundary exclusively
        /// </summary>
        /// <param name="upperBound">upper exclusive boundary</param>
        public static IFluentValidation<string> IsShorterThan(this IFluentValidation<string> fluentValidation, int upperBound)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Length >= upperBound);
        }
        /// <summary>
        /// Checks whether the collections length is equal to the provided length
        /// </summary>
        /// <param name="length">length to equal</param>
        public static IFluentValidation<ICollection> IsLengthEqualTo(this IFluentValidation<ICollection> fluentValidation, int length)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Count != length);
        }
        /// <summary>
        /// Checks whether the strings length is equal to the provided length
        /// </summary>
        /// <param name="length">length to equal</param>
        public static IFluentValidation<string> IsLengthEqualTo(this IFluentValidation<string> fluentValidation, int length)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Length != length);
        }
        /// <summary>
        /// Checks whether the collections length is not equal to the provided length
        /// </summary>
        /// <param name="length">length to equal</param>
        public static IFluentValidation<ICollection> IsLengthNotEqualTo(this IFluentValidation<ICollection> fluentValidation, int length)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Count == length);
        }
        /// <summary>
        /// Checks whether the strings length is not equal to the provided length
        /// </summary>
        /// <param name="length">length to equal</param>
        public static IFluentValidation<string> IsLengthNotEqualTo(this IFluentValidation<string> fluentValidation, int length)
        {
            return fluentValidation.PerformValidation(fv => fv.ValidatedValue.Length == length);
        }
        #endregion



        internal static IFluentValidation<T> PerformValidation<T>(this IFluentValidation<T> fluentValidation, Func<IFluentValidation<T>, bool> isInvalidFactory)
        { 
            if (fluentValidation.ValidationWasSuccessful && 
                fluentValidation.WasLastValidationConditionMet &&
                isInvalidFactory(fluentValidation))
                return fluentValidation.Invalidate();

            return fluentValidation;
        }
    }
}
